import UIKit

//泛型解决的问题
func swapTwoInts(_ a:inout Int, _ b: inout Int) {
    let tempA = a
    a = b
    b = tempA
}


var someInt = 3
var antherInt = 5
swapTwoInts(&someInt, &antherInt)
print("now someint is \(someInt), anther is \(antherInt)")

//泛型函数
//泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名（swapTwoValues(_:_:)）后面跟着占位类型名（T），并用尖括号括起来（<T>）。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名，因此 Swift 不会去查找名为 T 的实际类型。
func swapTwo<T>(_ a: inout T, _ b: inout T) {
    let tempA = a
    a = b
    b = tempA
}

var onestr = "lxy"
var twostr = "lsm"

swapTwo(&onestr, &twostr)
print("now onestr is \(onestr), twostr is \(twostr)")

//泛型类型 仿照UINavigationController类实现一个栈的功能
struct IntStack {
    var items: [Int] = []
    mutating func push(_ item:Int) { //mutating 是为了修改struct的属性：items
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

struct Stack<Element> {
    var items: [Element] = []
    mutating func push(_ item:Element) { //mutating 是为了修改struct的属性：items
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

let str1 = "aaa"
let str2 = "bbb"
let str3 = "ccc"
var stack = Stack(items: [])
stack.push(str1)
stack.push(str2)
stack.push(str3)

print("now stack have three element:\(stack.items)")
stack.pop()
print("now stack have three element:\(stack.items)")

//泛型扩展
extension Int {
    mutating func twoMultifyNumber() {
        self = self * 2
    }
}
var originNum = 3
originNum.twoMultifyNumber()

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

if let item = stack.topItem {
    print("item top is \(item)")
}

//类型约束   如果能对泛型函数或泛型类型中添加特定的类型约束，这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。

//类型约束语法

//func someFunction<T:SomeType, U:SomeProtocol>(someT: T,someU: U) {
//    //泛型函数的函数体部分 上面这个函数有两个类型参数。第一个类型参数 T 必须是 SomeClass 子类；第二个类型参数 U 必须符合 SomeProtocol 协议。
//}

//类型约束实践
func findIndex(ofString valueToFind:String, in array:[String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    
    return nil
}

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let index = findIndex(ofString: "llama", in: strings) {
    print("dog is find index is \(String(describing: index))")

}
//改造成泛型的 findIndex(of:in:) 类型参数写做 T: Equatable，也就意味着“任何符合 Equatable 协议的类型 T”。
func findIndex<T:Equatable>(ofString valueToFind:T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    
    return nil
}

let doubleIndex = findIndex(ofString: 9.3, in: [3.14159, 0.1, 0.25])
let stringIndex = findIndex(ofString: "Mike", in: ["Mike", "Malcolm", "Andrea"])

//关联类型 定义一个协议时，声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称，其代表的实际类型在协议被遵循时才会被指定。关联类型通过 associatedtype 关键字来指定。
protocol Container {
    associatedtype Item
    mutating func append(_ item:Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

//Container 协议声明了一个关联类型 Item，写作 associatedtype Item。协议没有定义 Item 是什么，这个信息留给遵从协议的类型来提供。尽管如此，Item 别名提供了一种方式来引用 Container 中元素的类型，并将之用于 append(_:) 方法和下标，从而保证任何 Container 的行为都能如预期。

struct IntStack1 : Container {
    var items: [Int] = []
    mutating func push(_ item: Int) {
           items.append(item)
       }
       mutating func pop() -> Int {
           return items.removeLast()
       }
    
    mutating func append(_ item: Int) {
        self.push(item)
    }
    
    

//    typealias Item = Int
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> Int {
        return items[i]
    }
}

struct Stack1<Element>: Container {
   
    // Stack<Element> 的原始实现部分
        var items: [Element] = []
        mutating func push(_ item: Element) {
            items.append(item)
        }
        mutating func pop() -> Element {
            return items.removeLast()
        }
    
    // Container 协议的实现部分
    typealias Item = Element
    
    var count: Int {
        return self.items.count
    }
    
    subscript(i: Int) -> Element {
        return self.items[i]
    }
    
    mutating func append(_ item: Element) {
        self.push(item)
    }
}

//扩展现有类型来指定关联类型
extension Array: Container {}


//给关联类型添加约束
protocol Container2 {
    associatedtype Item: Equatable // 要求关联类型 Item 必须遵循 Equatable 协议：

    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
//在关联类型约束里使用协议
protocol SuffixableContainer: Container {
    associatedtype Suffix:SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

//Suffix 是一个关联类型，就像上边例子中 Container 的 Item 类型一样。Suffix 拥有两个约束：它必须遵循 SuffixableContainer 协议（就是当前定义的协议），以及它的 Item 类型必须是和容器里的 Item 类型相同。Item 的约束是一个 where 分句
//extension Stack1: SuffixableContainer {
//    func suffix(_ size: Int) -> Stack1<Element> {
//        var result = Stack1()
//        for index in (count - 1)..<count {
//            result.append(self[index])
//        }
//        return result
//    }
//    
//    typealias Suffix = Stack1
//    
//}
	
//extension Stack1: SuffixableContainer {
//    func suffix(_ size: Int) -> Stack1 {
//        var result = Stack1()
//        for index in (count-size)..<count {
//            result.append(self[index])
//        }
//        return result
//    }
//    // 推断 suffix 结果是Stack。
//}
//var stackOfInts = Stack1<Int>()
//stackOfInts.append(10)
//stackOfInts.append(20)
//stackOfInts.append(30)
//let suffix = stackOfInts.suffix(2)
//print(suffix)

//泛型 Where 语句
func allItemMatch<C1: Container, C2 : Container>(_ someContainer:C1, _ anotherContainer:C2) -> Bool where C1.Item  == C2.Item, C1.Item:Equatable {
    if someContainer.count != anotherContainer.count {
        return false
    }
    
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    
    return true
}

var stackOfStrings = Stack1<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemMatch(stackOfStrings, arrayOfStrings) {
    print("all item matchd.")
} else {
    print("not all item matched")
}

//具有泛型 Where 子句的扩展
//使用泛型 where 子句可以为扩展添加新的条件，因此只有当栈中的元素符合 Equatable 协议时，扩展才会添加 isTop(_:) 方法。
extension Stack1 where Element: Equatable {
    func isTop(_ item:Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

if stackOfStrings.isTop("tres") {
    print("top element is tres.")
} else {
    print("Top element is something else.")
}

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
//notEquatableStack.isTop(notEquatableValue)  // 报错  如果尝试在其元素不符合 Equatable 协议的栈上调用 isTop(_:) 方法，则会收到编译时错误。

extension Container where Item: Equatable {
    func startWith(_ item:Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

if [9,9,9].startWith(9) {
    print("start with 9")
} else {
    print("do not start with 9")
}

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}

print("average is \([1.0,2.0,3.0].average())")

extension Container {
    func average() -> Double where Item == Int {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
    
    func endsWith(_ item:Item) -> Bool where Item:Equatable {
        return count >= 1 && self[count-1] == item
    }
}

let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// 输出 "648.75"
print(numbers.endsWith(37))
//上面等于与这么写,写2个扩展, 因为重命名报错，故注释掉代码

//extension Container where Item == Int {
//    func average() -> Double where Item == Int {
//        var sum = 0.0
//        for index in 0..<count {
//            sum += Double(self[index])
//        }
//        return sum / Double(count)
//    }
//}
//
//extension Container where Item:Equatable {
//    func endsWith(_ item:Item) -> Bool where Item:Equatable {
//        return count >= 1 && self[count-1] == item
//    }
//}

//泛型下标

extension Container {
    subscript<Indices: Sequence>(indics:Indices) -> [Item] where Indices.Iterator.Element == Int {
        var result:[Item] = []
        for index in indics {
            result.append(self[index])
        }
        return result
    }
}
//这个 Container 协议的扩展添加了一个下标方法，接收一个索引的集合，返回每一个索引所在的值的数组
//这个泛型下标的约束如下：
//
//在尖括号中的泛型参数 Indices，必须是符合标准库中的 Sequence 协议的类型。
//下标使用的单一的参数，indices，必须是 Indices 的实例。
//泛型 where 子句要求 Sequence（Indices）的迭代器，其所有的元素都是 Int 类型。这样就能确保在序列（Sequence）中的索引和容器（Container）里面的索引类型是一致的。




protocol eatContainer {
    associatedtype ItemType
    mutating func append(_ item:ItemType)
    var count:Int { get }
    subscript(i:Int)->ItemType { get }
}

struct eatStack<T> : eatContainer {
  
    var items = [T]()
    mutating func push(_ item: T) {
        items.append(item)
    }
    
    mutating func append(_ item: T) {
        self.push(item)
    }
    
    subscript(i: Int) -> T {
        return items[i]
    }
    
    var count: Int{
        return items.count
    }
    
}

func allItemMath<C1: eatContainer, C2: eatContainer
>(someContainer:C1, anotherContainer:C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType:Equatable   {
    if someContainer.count != anotherContainer.count {
        return false
    }
    
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}

extension Array: eatContainer {}


var stackOfString = eatStack<String>()
stackOfString.push("uno")
stackOfString.push("dos")
stackOfString.push("aa")
var arrayOfString = ["uno","dos","aa"]
if allItemMath(someContainer: stackOfString, anotherContainer: arrayOfString) {
    print(" equal ======")
} else {
    print(" not equal ")
}

//上面例子创建一个eatStack来存储String,然后压了三个字符串进栈。同事创建了一个Array数组，并初始化三个一样的字符串。即便栈和数组是不同的类型，但是他们都遵循eatContainer协议，而且他们都包含同样的类型值。因此可以用allItemMath函数，用这两个容器作为参数，实现比较元素是否相同的功能。

